/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.bhatni.mockprocessor;

import com.bhatni.mockprocessor.expression.BaseConditionExpression;
import com.bhatni.mockprocessor.expression.BaseGroupConditionExpression;
import com.bhatni.mockprocessor.expression.BaseSingleConditionExpression;
import com.bhatni.mockprocessor.expression.RegexConditionExpression;
import com.bhatni.mockprocessor.expression.AndGroupExpression;
import com.bhatni.mockprocessor.expression.OrGroupExpression;
import com.bhatni.mockprocessor.macro.BaseMacro;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author Viren
 */
public class Scenario {

    private String name;
    private BaseConditionExpression matchCriteria;
    private String outputTemplate;
    private List<BaseMacro> macros;
    private HashMap<String, String> header = new HashMap<String, String>();
    VelocityEngine velocityEngine;
    
    enum BodyType {
        template,
        source, 
        unknown
    }


    private BodyType bodyType;
    
    static class FixedFilenameFilter implements FilenameFilter {

        String filename;

        public FixedFilenameFilter(String filename) {
            this.filename = filename;
        }

        @Override
        public boolean accept(File file, String name) {
            return name.compareTo(filename) == 0;
        }
    }

    public Scenario(String name) {
        this.name = name;
        matchCriteria = null;
        macros = new ArrayList<BaseMacro>();
    }

    public String getName() {
        return name;
    }

    void addMacro(String macroType, String macroName, String value) {
        BaseMacro m = BaseMacro.createMacro(macroType, macroName, value);
        if (m != null) {
            macros.add(m);
        }
    }

    private void addHeaderProperty(String name, String value) {
        header.put(name, value);
    }

    void setOutputTemplate(String template) {
        outputTemplate = template;
    }

    boolean match(Message in) {
        return matchCriteria.evaluate(in);
    }

    void applyTo(Message in, Message out) {

        VelocityContext context = new VelocityContext();

        for (BaseMacro macro : macros) {
            context.put(macro.getName(), macro.getResolvedValue(in));
        }


        // body
        StringWriter w = new StringWriter();
        if (bodyType == BodyType.source) {
            velocityEngine.evaluate(context, w, "Scenario.applyTo", in.getBody().toString());
        }
        else 
        {
            Template t = velocityEngine.getTemplate(outputTemplate);
            t.merge(context, w);

        }
        out.setBody(w.toString());
        


        // header
        for (Map.Entry<String, String> entry : header.entrySet()) {
            StringWriter sw = new StringWriter();
            velocityEngine.evaluate(context, sw, "Scenario.applyTo", entry.getValue());
            out.setHeader(entry.getKey(), sw.toString());
        }



    }

    private String getOutputTemplate() {
        return outputTemplate;
    }
    
    static String getFileContent(File file) {
        FileReader fr = null;
        try {
            fr = new FileReader(file);
            char[] buf = new char[(int) file.length()];
            fr.read(buf);
            return new String(buf);
        } catch (IOException ex) {
            Logger.getLogger(Scenario.class.getName()).log(Level.SEVERE, null, ex);

        } finally {
            try {
                fr.close();
            } catch (IOException ex) {
                Logger.getLogger(Scenario.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return "";
    }

    /**
     * @return the matchCriteria
     */
    public BaseConditionExpression getMatchCriteria() {
        return matchCriteria;
    }

    /**
     * @param matchCriteria the matchCriteria to set
     */
    public void setMatchCriteria(BaseConditionExpression matchCriteria) {
        this.matchCriteria = matchCriteria;
    }

    public static Scenario load(Element element) {

        Scenario scenario = null;
        try {

            scenario = new Scenario(element.getAttribute("name"));

            XPath xPath = XPathFactory.newInstance().newXPath();

            //populate the match criteria
            Element eleExpression = (Element) xPath.evaluate("MatchCriteria/Expression|MatchCriteria/GroupExpression", element, XPathConstants.NODE);
            BaseConditionExpression matchExpression = getExpression(eleExpression);
            scenario.setMatchCriteria(matchExpression);

            //populate Macros
            NodeList macros = (NodeList) xPath.evaluate("OutputMessage/Macros/Macro", element, XPathConstants.NODESET);
            for (int i = 0; i < macros.getLength(); i++) {
                Element eleMacro = (Element) macros.item(i);
                scenario.addMacro(eleMacro.getAttribute("type"), eleMacro.getAttribute("name"), eleMacro.getTextContent());
            }

            // set the header
            NodeList properties = (NodeList) xPath.evaluate("OutputMessage/Header/Property", element, XPathConstants.NODESET);
            for (int i = 0; i < properties.getLength(); i++) {
                Element eleProperty = (Element) properties.item(i);
                scenario.addHeaderProperty(eleProperty.getAttribute("name"), eleProperty.getTextContent());
            }

            // set the body
            String typeBody = ((Element) xPath.evaluate("OutputMessage/Body", element, XPathConstants.NODE)).getAttribute("type");
            try {
                scenario.bodyType = BodyType.valueOf(typeBody.toLowerCase());
            }
            catch (IllegalArgumentException ex) {
                scenario.bodyType = BodyType.unknown;
            }
            
            if (scenario.bodyType == BodyType.template) {
                String templateFilename = ((Element) xPath.evaluate("OutputMessage/Body", element, XPathConstants.NODE)).getTextContent();
                scenario.setOutputTemplate(templateFilename);

            } else if (scenario.bodyType == BodyType.source) {
                scenario.setOutputTemplate("");
            }


        } catch (XPathExpressionException ex) {
            scenario = null;
            Logger.getLogger(Scenario.class.getName()).log(Level.SEVERE, null, ex);
        }
        return scenario;

    }

    static private BaseConditionExpression getExpression(Element eleExpression) {
        BaseConditionExpression condition = null;
        String tagName = eleExpression.getTagName();
        if (tagName.compareTo("Expression") == 0) {
            condition = getSingleConditionExpression(eleExpression);
        } else if (tagName.compareTo("GroupExpression") == 0) {
            condition = getGroupConditionExpression(eleExpression);
        }
        return condition;

    }

    static private BaseGroupConditionExpression getGroupConditionExpression(Element eleGroupExpression) {
        BaseGroupConditionExpression condition = null;
        String type = eleGroupExpression.getAttribute("type");
        if (type.compareTo("OR") == 0) {
            condition = new OrGroupExpression();
        } else {
            condition = new AndGroupExpression();
        }
        NodeList list = eleGroupExpression.getChildNodes();
        for (int i = 0; i < list.getLength(); i++) {
            Node node = list.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                condition.getExpressions().add(getExpression((Element) node));
            }

        }
        return condition;

    }

    static private BaseSingleConditionExpression getSingleConditionExpression(Element eleExpression) {
        BaseSingleConditionExpression condition = null;
        String type = eleExpression.getAttribute("type");
        if (type.compareTo("Regex") == 0) {
            condition = new RegexConditionExpression();
            condition.setExpression(eleExpression.getTextContent());
            if (eleExpression.getAttribute("content").startsWith("header")) {
                condition.setFactType(BaseSingleConditionExpression.FACT_TYPE.HEADER);
                condition.setFactFilter(eleExpression.getAttribute("content").substring(7));

            } else {
                condition.setFactType(BaseSingleConditionExpression.FACT_TYPE.BODY);
                condition.setFactFilter("");

            }
        }
        return condition;
    }
}
